package com.bizframework.springjpa.support.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;

import com.bizframework.core.domain.BaseDao;
import com.bizframework.core.domain.BaseEntity;
import com.bizframework.core.domain.ListResult;
import com.bizframework.core.domain.QueryCondition;
import com.bizframework.core.domain.QueryOperator;
import com.bizframework.core.domain.SortInfo;
import com.bizframework.core.util.StringUtils;

public class BaseDaoJPAImpl<T extends BaseEntity<IDT>, IDT extends Serializable>
		extends JpaDaoSupport implements BaseDao<T, IDT> {

	protected Class<T> entityClass;

	protected Log log = LogFactory.getLog(getClass());

	@SuppressWarnings("unchecked")
	public BaseDaoJPAImpl() {
		super();
		if (((ParameterizedType) getClass().getGenericSuperclass())
				.getActualTypeArguments()[0] != null) {
			entityClass = (Class<T>) ((ParameterizedType) getClass()
					.getGenericSuperclass()).getActualTypeArguments()[0];
		}
	}

	public void delete(T entity) throws Exception {
		this.getJpaTemplate().remove(entity);
	}

	public List<T> findBy(String name, Object value) throws Exception {
		return this.findBy(new String[] { name }, new Object[] { value });
	}

	@SuppressWarnings("unchecked")
	public List<T> findBy(String[] names, Object[] values) throws Exception {
		Map<String, Object> params = new HashMap<String, Object>();
		String alias = entityClass.getName().toLowerCase().replace(".", "_");
		StringBuilder qStr = new StringBuilder(" where ");
		for (int i = 0; i < names.length; i++) {
			params.put(names[i], values[i]);
			qStr.append(alias).append(".").append(names[i]).append("=:")
					.append(names[i]);
			
			if (i < names.length - 1) {
				qStr.append(" And ");
			}
		}
		String queryString = "from " + entityClass.getName() + " as " + alias
				+ qStr.toString();
		return this.getJpaTemplate().findByNamedParams(queryString, params);
	}

	public void flush() throws Exception {
		this.getJpaTemplate().flush();
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() throws Exception {
		String queryString = "from " + entityClass.getName();
		return getJpaTemplate().find(queryString);
	}

	@SuppressWarnings("unchecked")
	public List<T> getByConditions(final List<QueryCondition> conditions,
			final int startIndex, final int count, final SortInfo sortInfo)
			throws Exception {
		return (List<T>) this.getJpaTemplate().executeFind(new JpaCallback() {
			public Object doInJpa(EntityManager em) throws PersistenceException {
				try {
					QueryParams params = createQueryParams(null, null,
							conditions, sortInfo);
					params.setHql(params.getHql());
					Query query = createQuery(em, params);
					query.setFirstResult(startIndex);
					if (count >= 0) {
						query.setMaxResults(count);
					}
					return query.getResultList();
				} catch (Exception e) {
					throw new PersistenceException();
				}
			}
		});
	}

	public List<T> getByConditions(List<QueryCondition> conditions,
			SortInfo sortInfo) throws Exception {
		return this.getByConditions(conditions, 0, -1, sortInfo);
	}

	public ListResult<T> getByConditionsWithCount(
			List<QueryCondition> conditions, int startIndex, int count,
			SortInfo sortInfo) throws Exception {
		ListResult<T> result = new ListResult<T>(this.getByConditions(
				conditions, startIndex, count, sortInfo));
		result.setTotalCount(this.getCountByConditions(conditions));
		return result;
	}

	public T getById(IDT entityId) throws Exception {
		List<T> results = this.findBy("id", entityId);
		if (results.size() == 0) {
			return null;
		} else {
			return results.get(0);
		}
	}

	public int getCountByConditions(final List<QueryCondition> conditions)
			throws Exception {
		return (Integer) this.getJpaTemplate().execute(new JpaCallback() {
			public Object doInJpa(EntityManager em) throws PersistenceException {
				try {
					QueryParams params = createQueryParams(null, null,
							conditions, null);
					params.setHql("Select count(*) " + params.getHql());
					Query query = createQuery(em, params);
					Long count = (Long) query.getSingleResult();
					return count == null ? 0 : Integer.parseInt(count
							.toString());
				} catch (Exception e) {
					throw new PersistenceException();
				}
			}
		});
	}

	public void refresh(T entity) throws Exception {
		getJpaTemplate().refresh(entity);
	}

	public void save(T entity) throws Exception {
		this.getJpaTemplate().persist(entity);
	}

	public void saveOrUpdate(T entity) throws Exception {
		if (this.getById(entity.getId()) == null) {
			getJpaTemplate().persist(entity);
		} else {
			getJpaTemplate().merge(entity);
		}
	}

	public void update(T entity) throws Exception {
		this.getJpaTemplate().merge(entity);
	}

	public T merge(T entity) {
		return this.getJpaTemplate().merge(entity);
	}

	protected Query createQuery(EntityManager entityManager, QueryParams params) {
		Query query = entityManager.createQuery(params.getHql());
		for (String key : params.getPrams().keySet()) {
			query.setParameter(key, params.getPrams().get(key));
		}
		return query;
	}

	/**
	 * 
	 * @param conditionFieldMap
	 * @param defalutAlias
	 * @param conditions
	 * @param sortInfo
	 * @return
	 * @throws Exception
	 */
	protected QueryParams createQueryParams(
			HashMap<String, String> conditionFieldMap, String defalutAlias,
			List<QueryCondition> conditions, SortInfo sortInfo)
			throws Exception {

		if (entityClass == null) {
			throw new Exception("entityClass为空");
		}

		QueryParams result = new QueryParams();

		if (StringUtils.isNullOrEmpty(defalutAlias)) {
			defalutAlias = entityClass.getName().toLowerCase()
					.replace(".", "_");
		}

		// 得到不带条件的hql
		String hql = "From " + entityClass.getName() + " as " + defalutAlias
				+ " Where 1=1";

		// 生成查询条件
		String conditonString = "";
		if (conditions != null) {
			for (QueryCondition condition : conditions) {
				if (condition == null)
					continue;

				String c = "";
				// 生成查询字段名
				String field = defalutAlias + "." + condition.getFieldName();
				if (conditionFieldMap != null
						&& conditionFieldMap.containsKey(condition
								.getFieldName())) {
					field = conditionFieldMap.get(condition.getFieldName());
				}

				// 生成参数名称
				String paraName = defalutAlias + "_" + condition.getFieldName()
						+ condition.hashCode();
				paraName = paraName.replace(".", "_");

				// 如果查询值为空值，操作符智能为 is 或者 is not，参数可省略。
				if (condition.getFieldValueString() == null
						&& condition.getFieldValue() == null) {
					if (condition.getQueryOperator() == QueryOperator.Equal) {
						c = "{0} is null".replace("{0}", field);
					} else if (condition.getQueryOperator() == QueryOperator.NoEqual) {
						c = "{0} is not null".replace("{0}", field);
					}
				} else {

					if (condition.getQueryOperator() == QueryOperator.In) {
						if (condition.getFieldValue() instanceof Collection<?>) {
						} else if (condition.getFieldValue() instanceof Object[]) {
						}
					}
					switch (condition.getFieldType()) {
					case Date:
						// 如果字段类型为“日期”，并且操作符为“等于”，条件就是>=当日0时0分，<次日0时0分
						if (condition.getQueryOperator().equals(
								QueryOperator.Equal)) {
							c = "({0}>={1} and {0}<{2})".replace("{0}", field)
									.replace("{1}", ":" + paraName + "_b")
									.replace("{2}", ":" + paraName + "_e");

							Date v = (Date) (condition.getFieldValue() == null ? condition
									.parseFieldValueString() : condition
									.getFieldValue());
							result.addQueryPram(paraName + "_b", v);
							result.addQueryPram(paraName + "_",
									DateUtils.addDays(v, 1));
						} else {
							c = condition.getQueryOperator()
									.replace("{0}", field)
									.replace("{1}", ":" + paraName);
							result.addQueryPram(
									paraName,
									condition.getFieldValue() == null ? condition
											.parseFieldValueString()
											: condition.getFieldValue());
						}
						break;
					default:
						c = condition.getQueryOperator().replace("{0}", field)
								.replace("{1}", ":" + paraName);
						result.addQueryPram(
								paraName,
								condition.getFieldValue() == null ? condition
										.parseFieldValueString() : condition
										.getFieldValue());
						break;
					}
				}
				if (StringUtils.isNullOrEmpty(conditonString)) {
					conditonString += " " + c;
				} else {
					conditonString += " " + condition.getQueryLogic() + " " + c;
				}
			}
		}
		if (!StringUtils.isNullOrEmpty(conditonString)) {
			hql += " and (" + conditonString + ")";
		}

		if (sortInfo != null) {
			if (sortInfo.getName() != null && sortInfo.getName() != "") {
				String dir = sortInfo.getSortDirection().toString();

				String sortField = defalutAlias + "." + sortInfo.getName();
				if (conditionFieldMap != null
						&& conditionFieldMap.containsKey(sortInfo.getName())) {
					sortField = conditionFieldMap.get(sortInfo.getName());
				}
				hql += " Order By " + sortField + " " + dir;
			}
		}

		result.setHql(hql);
		return result;
	}

	public class QueryParams {
		private String hql;

		private Map<String, Object> prams = new HashMap<String, Object>();

		public String getHql() {
			return hql;
		}

		public void setHql(String hql) {
			this.hql = hql;
		}

		public Map<String, Object> getPrams() {
			return prams;
		}

		public void setPrams(Map<String, Object> prams) {
			this.prams = prams;
		}

		public void addQueryPram(String name, Object value) {
			prams.put(name, value);
		}
	}
}
